Skip to content

feat(rules): add warn-curl-mutating-supabase-rest (close feedback_scripts_not_db.md)#16

Merged
lapc506 merged 4 commits into
mainfrom
feat/hook-curl-mutating-supabase-rest
May 10, 2026
Merged

feat(rules): add warn-curl-mutating-supabase-rest (close feedback_scripts_not_db.md)#16
lapc506 merged 4 commits into
mainfrom
feat/hook-curl-mutating-supabase-rest

Conversation

@lapc506
Copy link
Copy Markdown
Collaborator

@lapc506 lapc506 commented May 10, 2026

Summary

Adds a single warn rule to the Database / migration discipline family
that closes the last documented escape hatch from feedback_scripts_not_db.md:
direct mutating curl calls against the Supabase PostgREST endpoint.

The existing warn-psql-against-supabase-remote rule covers the
psql / pg_dump / pg_restore vector. warn-curl-mutating-supabase-rest
handles the equivalent REST escape hatch.

Why this matters

curl -X POST/PATCH/PUT/DELETE https://<project>.supabase.co/rest/v1/...
mutations:

  • bypass the versioned migrations workflow (drift across local DBs / envs)
  • skip the RLS + admin-role checks the Supabase clients enforce
  • circumvent the API client layer in src/services/api/ that already
    encapsulates the same access pattern

The new rule

Rule Action Tool surface Intent
warn-curl-mutating-supabase-rest warn Bash Nudge against curl -X (POST|PATCH|PUT|DELETE) ... .supabase.co/rest/v1/. GET is not flagged (read-only). Bypass marker curl-supabase-rest-mutation for documented hotfixes.

The rule is warn, not block, mirroring the severity of its neighbor
warn-psql-against-supabase-remote. Both warn against the same class of
nudge (ad-hoc DB writes that bypass migrations) and both are escalatable
to block in a follow-up PR if usage data shows the warning is ignored.

Test count

```
Before: 86 / 86 passed (18 rules)
After: 93 / 93 passed (19 rules, +7 new tests)
```

The 7 new tests cover positive matches (POST, PATCH, PUT, DELETE),
negative matches (GET, non-Supabase hosts), and bypass-marker behavior.

Version bump

`1.7.0 -> 1.8.0`

Per semver, adding a new enforcement rule is a feature add -> minor bump.
Bumped in:

  • `package.json`
  • `.claude-plugin/plugin.json`
  • `.claude-plugin/marketplace.json` (top-level + nested plugin entry)

Test plan

  • `npm run build-rules` regenerates `rules.json` (18 -> 19 rules)
  • `npm run test-hooks` 93 / 93 pass
  • CI on `feat/hook-curl-mutating-supabase-rest` (Blacksmith runner)
  • Greptile review -> 5 / 5

Notes for reviewers

  • Test fixtures use a sanitized Supabase host (`example.supabase.co`),
    same convention as the migration-discipline rules added in PR feat(rules): add 4 migration-discipline PreToolUse rules #15
    round-2 (no real project refs in the public toolkit).
  • Match anchored on the explicit `-X METHOD` form. `curl --request POST`
    is an accepted false negative; the long form is uncommon enough that
    authors using it can be expected to know the rule and self-flag.
  • Match narrowed to `/rest/v1/` so Auth / Storage / Edge Function
    endpoints are not flagged. Edge Functions are the intended outlet.

OpenSpec: `openspec/changes/2026-05-hook-curl-mutating-supabase-rest/`
(proposal, design, tasks).

This PR is part of a sequential rollout — see the planning thread for
PR-B (Tier 1 anti-foot-shoot blocks) and PR-C (Tier 2 discipline warns)
queued behind it.

Created by Claude Code on behalf of @lapc506

Claude Code

…ipts_not_db.md)

Adds a single warn rule to the migration-discipline family that catches the
last documented escape hatch from the "no ad-hoc DB writes" memory:
direct curl mutations (POST / PATCH / PUT / DELETE) against the Supabase
PostgREST endpoint.

The neighbor warn-psql-against-supabase-remote already covers the psql /
pg_dump / pg_restore vector; warn-curl-mutating-supabase-rest closes the
final gap. Match is anchored on the explicit `-X <METHOD>` form and
narrowed to `.supabase.co/rest/v1/` so Auth, Storage, and Edge Function
endpoints are not flagged.

Test fixtures use sanitized hosts (example.supabase.co) — same convention
the migration-discipline rules adopted in PR #15 round-2.

- 19 rules (was 18)
- 93 / 93 tests pass (was 86, +7)
- 1.7.0 -> 1.8.0 in package.json, plugin.json, marketplace.json (top-level
  + nested plugin entry)
- README family entry extended with the new rule
@lapc506
Copy link
Copy Markdown
Collaborator Author

lapc506 commented May 10, 2026

@greptile review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 10, 2026

Greptile Summary

This PR adds the warn-curl-mutating-supabase-rest hook rule, closing the last escape hatch documented in feedback_scripts_not_db.md — direct curl mutations against the Supabase PostgREST endpoint — and bumps the plugin version to 1.8.0 across all three manifests. The rule joins the existing warn-psql-against-supabase-remote in the Database/migration discipline family at the same warn severity.

  • New rule (hooks/rules/rules.yaml, rules.json): multi-clause POSIX regex catches explicit -X METHOD, no-space -XPOST, URL-before-flag ordering, and implicit POST via -d/--data*; GET and non-/rest/v1/ endpoints are intentionally excluded; 12 tests added.
  • Version bump: 1.7.0 → 1.8.0 applied consistently across package.json, .claude-plugin/plugin.json, and .claude-plugin/marketplace.json.
  • OpenSpec docs: full change lifecycle documented, though design.md's Pattern section retains the original single-clause regex rather than the evolved multi-clause implementation.

Path to 5/5 Confidence

  1. Update design.md Pattern section (openspec/changes/2026-05-hook-curl-mutating-supabase-rest/design.md, lines 5–20): Replace the stale single-clause pattern with the actual multi-clause pattern from rules.yaml line 1008. Also extend the Rationale subsection to describe the URL-before-flag and implicit -d/--data* POST vectors added in Greptile rounds 1 and 2.

  2. Handle or document --data-urlencode (hooks/rules/rules.yaml, line 1008): Either extend the --data(...) alternation to include -urlencode[[:space:]] and -urlencode=, or add an explicit comment calling it out as a known accepted false negative (parallel to the --request acknowledgement in design.md line 14). Add one test case to confirm the chosen behavior.

Confidence Score: 5/5

Safe to merge; the rule logic and all 12 tests are correct, and the only gaps are a stale pattern in a design doc and an undocumented false negative for --data-urlencode.

The regex correctly catches all the mutation vectors it claims to cover, all 12 tests are well-structured and validate both positive and negative paths, version bumps are consistent, and the bypass marker is properly wired. The two issues found are a documentation artefact (design.md still shows the old pattern) and one undocumented false negative (--data-urlencode implicit POST), neither of which affects runtime behaviour of the deployed rule.

openspec/changes/2026-05-hook-curl-mutating-supabase-rest/design.md — the Pattern section is out of date with the implementation.

Important Files Changed

Filename Overview
hooks/rules/rules.yaml Adds warn-curl-mutating-supabase-rest rule with a multi-clause pattern covering -XPOST no-space, URL-before-flag ordering, and implicit POST via -d/--data*; --data-urlencode implicit POST is not caught (undocumented gap)
hooks/rules/rules.json Regenerated artifact that mirrors rules.yaml; same pattern gap applies
openspec/changes/2026-05-hook-curl-mutating-supabase-rest/design.md Pattern code block and rationale section still reflect the original single-clause regex from before the Greptile revision rounds; the test list is correct but the Pattern section is stale
hooks/rules/README.md Database/migration discipline family entry extended to include the new rule; description is accurate and consistent with the implementation
package.json Version bumped 1.7.0 → 1.8.0; no other changes

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Bash tool_call: curl command] --> B{Contains supabase.co/rest/v1/?}
    B -- No --> ALLOW[Allow: exit 0, no warning]
    B -- Yes --> C{Bypass marker present?}
    C -- Yes --> ALLOW
    C -- No --> D{Vector 1: explicit -X METHOD}
    D -- POST/PATCH/PUT/DELETE --> WARN
    D -- GET or absent --> E{Vector 2: URL before -X METHOD}
    E -- Yes --> WARN
    E -- No --> F{Vector 3: implicit POST via -d or --data flags}
    F -- Matched --> WARN
    F -- No match --> ALLOW
    WARN[warn: exit 0 + stderr contains rule id]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
openspec/changes/2026-05-hook-curl-mutating-supabase-rest/design.md:5-6
**Stale pattern in Design doc**

The "Pattern" code block still shows the original single-clause regex (`curl.*-X[[:space:]]+(POST|PATCH|PUT|DELETE).*\.supabase\.co/rest/v1/`), which is the version from before the Greptile rounds that added the no-space `-XPOST`, URL-before-flag, and implicit-`-d` variants. The rationale text below the box also only describes the `-X METHOD` form and doesn't mention the `-d`/`--data*` implicit-POST vector. Anyone reading `design.md` to understand the rule's scope or to write a follow-up rule gets a materially incomplete picture of what the implementation actually matches.

### Issue 2 of 2
hooks/rules/rules.yaml:1007-1009
**`--data-urlencode` implicit POST not caught**

`curl --data-urlencode 'name=Alice' https://example.supabase.co/rest/v1/profiles` triggers an implicit POST (same as `-d`) but falls through the pattern. The `--data([[:space:]]|=|-raw...|binary...)` alternation requires the character after `--data` to be a space, `=`, `-raw`, or `-binary`; `-urlencode` matches none of those, so the flag is silently skipped. This is in the same class as the `--request` acknowledged false negative, but unlike `--request`, `--data-urlencode` is commonly used for form-style payloads against REST endpoints and isn't called out anywhere in the design doc as a known gap.

Reviews (3): Last reviewed commit: "fix(rules): close round-2 Greptile findi..." | Re-trigger Greptile

Comment thread hooks/rules/rules.yaml Outdated
Comment thread hooks/rules/rules.yaml Outdated
Comment thread hooks/rules/rules.yaml Outdated
…rest

1. Cover the no-space `-XPOST` shorthand: relax `[[:space:]]+` to
   `[[:space:]]*`. Adds `warns-curl-xpost-no-space` test.

2. Cover URL-before-flag ordering: extend the pattern to a two-clause
   alternation that matches whether `-X METHOD` precedes or follows the
   `.supabase.co/rest/v1/` segment. Adds `warns-curl-url-before-flag`
   test.

3. Rename the four `blocks-*` test names to `warns-*` — the rule action
   is `warn` (expected_exit 0), so `blocks-*` was misleading.

Tests: 93 -> 95 passing (+2 coverage tests).
@lapc506
Copy link
Copy Markdown
Collaborator Author

lapc506 commented May 10, 2026

Round 2: addressed all 3 Greptile findings. See commit 5ff4156.

  1. -XPOST no-space gap — relaxed [[:space:]]+ -> [[:space:]]*. New test warns-curl-xpost-no-space.
  2. URL-before-flag ordering — extended the pattern to a two-clause alternation (-X METHOD ... rest/v1/ OR rest/v1/ ... -X METHOD). New test warns-curl-url-before-flag.
  3. blocks-* -> warns-* rename — applied to all 4 method tests; naming now matches the warn action.

Tests: 93 -> 95 passing.

@greptile review

…abase-rest

1. **Implicit POST via -d/--data is now caught** — extend the pattern
   alternation to fire when `-d`, `--data`, `--data-raw`, or
   `--data-binary` are present without `-X`. Curl auto-promotes to POST
   in that case, so a one-liner like
     curl -d '{"id":1}' https://<project>.supabase.co/rest/v1/profiles
   would otherwise pass silently. Three new tests cover -d before URL,
   --data-raw before URL, and URL before -d.

2. **design.md test list refreshed** — replace the stale `blocks-*`
   names with the actual `warns-*` names and list all 12 tests after
   rounds 1-2. Drop the old "blocks- naming for symmetry" rationale
   that was made obsolete by round 1.

3. **Pattern comment refactored** — document the three vectors the
   alternation covers (explicit -X, URL ordering, implicit POST via
   data flags).

Tests: 95 -> 98 passing.
@lapc506
Copy link
Copy Markdown
Collaborator Author

lapc506 commented May 10, 2026

Round 3: addressed both round-2 findings (implicit POST via -d/--data* + stale design.md test list). See commit ac5b931. Tests 95 -> 98.

@greptile review

…abase-rest

1. **--data-urlencode now caught** — extend the `--data(...)` alternation
   to include `-urlencode[[:space:]]` and `-urlencode=`. Curl
   auto-promotes to POST for `--data-urlencode` the same way it does for
   `-d`/`--data`/`--data-raw`/`--data-binary`, so a one-liner like
     curl --data-urlencode 'name=Alice' https://<project>.supabase.co/rest/v1/profiles
   would otherwise pass silently. Adds `warns-curl-implicit-post-via-data-urlencode`
   test.

2. **design.md Pattern section refreshed** — replace the original
   single-clause regex with the actual 4-clause alternation, documenting
   each vector (explicit -X, URL ordering, implicit POST via data flags,
   implicit POST via --data-urlencode). The accepted false-negatives
   subsection now also mentions `-G` (which overrides auto-promotion).

Tests: 98 -> 99 passing. Greptile already at 5/5 on round 3 commit
ac5b931 — these are belt-and-suspenders fixes for the two nits Greptile
flagged in its 5/5 review.
@lapc506 lapc506 merged commit 1886a3b into main May 10, 2026
1 check passed
@lapc506 lapc506 deleted the feat/hook-curl-mutating-supabase-rest branch May 10, 2026 06:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant